/**
 * Bootstrap based calendar full view.
 *
 * https://github.com/Serhioromano/bootstrap-calendar
 *
 * User: Sergey Romanov <serg4172@mail.ru>
 * Version 0.1
 */
"use strict";

Date.prototype.getWeek = function() {
    var onejan = new Date(this.getFullYear(), 0, 1);
    return Math.ceil((((this.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
}
Date.prototype.getMonthFormatted = function() {
    var month = this.getMonth() + 1;
    return month < 10 ? '0' + month : month;
}
Date.prototype.getDateFormatted = function() {
    var date = this.getDate();
    return date < 10 ? '0' + date : date;
}

String.prototype.capitalize = function () {
    return this.charAt(0).toUpperCase() + this.substr(1).toLowerCase();
}

;(function($) {

    var defaults = {
        width: '100%',      // maximum width of all calendar
        view: 'month',      // month, week, day
        day: 'now',  // what day to start with. No matter month, week or day this will be a starting point
        // format yyyy-mm-dd or now

        /**
        * Which day is the first day of the week.
        * 1 - monday
        * 2 - sunday
        */
        firstDay: 1,
        eventsUrl: '',     // URL to return JSON list of events in special format.
        // {success:1, result: [....]} or for error {success:0, error:'Something terrible happened'}
        // events: [...] as described in events property description
        // The start and end variables will be sent to this url

        /**
         * path to templates should end with slash /. It can be as relative
         *
         * /component/bootstrap-calendar/tmpls/
         *
         * or absolute
         *
         * http://localhost/component/bootstrap-calendar/tmpls/
         */
        tmplPath: 'tmpls/',
        classes: {
            months: {
                inmonth: 'cal-day-inmonth',
                outmonth: 'cal-day-outmonth',
                saturday: 'cal-day-weekend',
                sunday: 'cal-day-weekend',
                holidays: 'cal-day-holiday',
                today: 'cal-day-today'
            },
            week: {
                workday: 'cal-day-workday',
                saturday: 'cal-day-weekend',
                sunday: 'cal-day-weekend',
                holidays: 'cal-day-holiday',
                today: 'cal-day-today'
            }
        },
        holidays: {
            '08-03': 'International Women\'s Day',
            '25-12': 'Christmas\'s',
            '01-05': "International labor day"
        },
        enable_easter_holidays: false, // Set to true if you want to enable Easter and Easter Monday as holidays
        views: {
            year: {
                slide_events: 1
            },
            month: {
                slide_events: 1
            }
        },

        // -------------------------------------------------------------
        // INTERNAL USE ONLY. DO NOT ASSIGN IT WILL BE OVERRIDDEN ANYWAY
        // -------------------------------------------------------------
        events: [],
        position: {
            start: new Date(),
            end: new Date()
        },
        templates: {
            year: '',
            month: '',
            week: '',
            day: ''
        }
    };
    
    /**
    * @class Calendar
    * @classdesc Class calendar, responsible to manage and render the full
    * calendar component.
    */
    function Calendar(params, context) {
        var me = this;

        me.options = $.extend(true, {}, defaults, params);

        // context = the container of the calendar
        me.context = context;

        context.css('width', me.options.width);
        $.ajaxSetup({dataType: 'json', type: 'post', async: false});
        var stop_cycling = false;

        /* PRIVATE FUNCTIONS */
        var render = function() {
            me.context.html('');
            loadTemplate(me.options.view);

            var data = {};
            data.events = [];
            data.cal = me;
            data.day = 1;

            // Getting list of days in a week in correct order. Works for month and week views
            if(me.options.firstDay == 1) {
                data.months = [language.d1, language.d2, language.d3, language.d4, language.d5, language.d6, language.d0]
            } else {
                data.months = [language.d0, language.d1, language.d2, language.d3, language.d4, language.d5, language.d6]
            }

            // Get all events between start and end
            var start = parseInt(me.options.position.start.getTime());
            var end = parseInt(me.options.position.end.getTime());

            $.each(me.options.events, function(k, event) {
                if((parseInt(event.start) < end) && (parseInt(event.end) > start)) {
                    data.events.push(event);
                }
            });

            switch(me.options.view) {
                case 'month':
                    break;
                case 'week':
                    break;
                case 'day':
                    break;
            }

            data.start = new Date(me.options.position.start.getTime());
            data.lang = language;

            me.context.append(me.options.templates[me.options.view](data));
            update();
        };

        /* TEMPLATE */
        this._week = function(event) {
            loadTemplate('week-days');

            var t = {};
            var start = parseInt(me.options.position.start.getTime());
            var end = parseInt(me.options.position.end.getTime());
            var events = [];

            $.each(me.options.events, function(k, event) {
                if((parseInt(event.start) < end) && (parseInt(event.end) > start)) {

                    event.start_day = new Date(parseInt(event.start)).getDay();
                    if(me.options.firstDay == 1) {
                        event.start_day = event.start_day - 1;
                    }
                    if(me.options.start_day < 0) {
                        event.start_day = 0;
                    }
                    if((event.end - event.start) <= 86400000) {
                        event.days = 1;
                    } else {
                        event.days = ((event.end - event.start) / 86400000);
                    }

                    if(event.start < start) {

                        event.days = event.days - ((start - event.start) / 86400000);
                        event.start_day = 0;
                    }

                    event.days = Math.ceil(event.days);

                    if(event.start_day + event.days > 7) {
                        event.days = 7 - (event.start_day);
                    }

                    if(me.options.firstDay == 1) {

                    }
                    events.push(event);
                }
            });
            t.events = events;
            return me.options.templates['week-days'](t);
        };

        this._month = function(month) {
            loadTemplate('year-month');

            var t = {};
            var newmonth = month + 1;
            t.data_day = me.options.position.start.getFullYear() + '-' + (newmonth < 10 ? '0' + newmonth : newmonth) + '-' + '01';
            t.month_name = language['m' + month];

            var curdate = new Date(me.options.position.start.getFullYear(), month, 1, 0, 0, 0);
            var start = parseInt(curdate.getTime());
            var end = parseInt(new Date(me.options.position.start.getFullYear(), month + 1, 0, 0, 0, 0).getTime());
            var events = [];

            $.each(me.options.events, function(k, event) {
                if((parseInt(event.start) < end) && (parseInt(event.end) > start)) {
                    events.push(event);
                }
            });
            t.events = events;
            return me.options.templates['year-month'](t);
        };

        this._day = function(week, day) {
            loadTemplate('month-day');

            var t = {tooltip: ''};
            var cls = me.options.classes.months.outmonth;

            var firstday = me.options.position.start.getDay();
            if(me.options.firstDay == 2) {
                firstday++;
            } else {
                firstday = (firstday == 0 ? 7 : firstday);
            }

            day = (day - firstday) + 1;
            var curdate = new Date(me.options.position.start.getFullYear(), me.options.position.start.getMonth(), day, 0, 0, 0);

            // if day of the current month
            if(day > 0) {
                cls = me.options.classes.months.inmonth;
            }
            // stop cycling table rows;
            if((day + 1) > me.options.position.end.getDate()) {
                stop_cycling = true;
            }
            // if day of the next month
            if(day > me.options.position.end.getDate()) {
                day = day - me.options.position.end.getDate();
                cls = me.options.classes.months.outmonth;
            }

            cls = $.trim(cls + " " + me._getDayClass("months", curdate));
            
            if(day <= 0) {
                var daysinprevmonth = (new Date(me.options.position.start.getFullYear(), me.options.position.start.getMonth(), 0)).getDate();
                day = daysinprevmonth - Math.abs(day);
                cls += ' cal-month-first-row';
            }

            var holiday = me._getHoliday(curdate);
            if(holiday !== false) {
                t.tooltip = holiday;
            }

            t.data_day = curdate.getFullYear() + '-' + curdate.getMonthFormatted() + '-' + (day < 10 ? '0' + day : day);
            t.cls = cls;
            t.day = day;

            var start = parseInt(curdate.getTime());
            var end = parseInt(start + 86400000);
            var events = [];

            $.each(me.options.events, function(k, event) {
                if((parseInt(event.start) < end) && (parseInt(event.end) > start)) {
                    events.push(event);
                }
            });

            t.events = events;
            return me.options.templates['month-day'](t);
        };

        this._getHoliday = function(date) {
           if(me.options.enable_easter_holidays) {
               var easter = getEasterDate(date.getFullYear());
               if(easter.toDateString() == date.toDateString()) {
                   return language.easter;
               }
               var easterMonday = new Date();
               easterMonday.setTime(easter.getTime());
               easterMonday.setDate(easter.getDate() + 1);
               if(easterMonday.toDateString() == date.toDateString()) {
                   return language.easterMonday;
               }
           }
           if(me.options.holidays) {
               var date_str = date.getDateFormatted() + '-' + date.getMonthFormatted();
               if(date_str in me.options.holidays) {
                   return me.options.holidays[date_str];
               }
               date_str += '-' + date.getFullYear();
               if(date_str in me.options.holidays) {
                   return me.options.holidays[date_str];
               }
           }
           return false;
        };

        this._getHolidayName = function(date) {
            var holiday = me._getHoliday(date);
            return (holiday === false) ? "" : holiday;
        };
        this.getHolidayName = this._getHolidayName;

        this._getDayClass = function(class_group, date) {
            var addClass = function(which, to) {
                var cls;
                cls = (me.options.classes && (class_group in me.options.classes) && (which in me.options.classes[class_group])) ? me.options.classes[class_group][which] : "";
                if((typeof(cls) == "string") && cls.length) {
                    to.push(cls);
                }
            };
            var classes = [];
            if(date.toDateString() == (new Date()).toDateString()) {
                addClass("today", classes);
            }
            var holiday = me._getHoliday(date);
            if(holiday !== false) {
                addClass("holidays", classes);
            }
            switch(date.getDay()) {
                case 0:
                    addClass("sunday", classes);
                    break;
                case 6:
                   addClass("saturday", classes);
                   break;
            }
            return classes.join(" ");
        };

        var initPosition = function() {
            var year, month, day;

            if(me.options.day == 'now') {
                var date = new Date();
                year = date.getFullYear();
                month = date.getMonth();
                day = date.getDate();
            } else if(me.options.day.match(/^\d{4}-\d{2}-\d{2}$/g)) {
                var list = me.options.day.split('-');
                year = list[0];
                month = list[1] - 1;
                day = list[2];
            }
            else {
                $.error(language.error_dateformat.format(me.options.day));
            }

            switch(me.options.view) {
                case 'year':
                    me.options.position.start.setTime(new Date(year, 0, 1).getTime());
                    me.options.position.end.setTime(new Date(year, 12, 0, 23, 59, 59).getTime());
                    break;
                case 'month':
                    me.options.position.start.setTime(new Date(year, month, 1).getTime());
                    me.options.position.end.setTime(new Date(year, month + 1, 0, 23, 59, 59).getTime());
                    break;
                case 'day':
                    me.options.position.start.setTime(new Date(year, month, day).getTime());
                    me.options.position.end.setTime(new Date(year, month, day, 23, 59, 59).getTime());
                    break;
                case 'week':
                    var curr = new Date(year, month, day);
                    var first = curr.getDate() - curr.getDay();
                    if(me.options.firstDay == 1) first += 1;
                    var last = first + 6;

                    me.options.position.start.setTime(new Date(year, month, first).getTime());
                    me.options.position.end.setTime(new Date(year, month, last, 23, 59, 59).getTime());
                    break;
                default:
                    $.error(language.error_noview.format(me.options.view))
            }
            return me;
        };

        /**
        * Loads the events for the calendar view.
        * It makes an AJAX call to the passed {@link Calendar#eventsUrl}
        * 
        * @fires Calendar#beforeeventsload
        */
        function loadEvents() {
            if(!me.options.eventsUrl) {
                $.error(language.error_loadurl);
            }
            var jMe = $(me);

            /**
            * Called when the calendar is ready to load more events.
            * Return false from any handler to cancel the loading.
            * 
            * @event Calendar#beforeeventsload
            */
            if (jMe.triggerHandler('beforeeventsload') === false) return;

            $.ajax({
                url: me.options.eventsUrl,
                data: {
                    from: me.options.position.start.getTime(),
                    to: me.options.position.end.getTime()
                }
            }).done(function(json) {
                if(!json.success) {
                    $.error(json.error);
                }
                me.options.events = json.result;

                /**
                * Called when the calendar succesfuly downloaded a new range of events.
                * 
                * @event Calendar#eventsload
                */
                jMe.triggerHandler('eventsload', me.options.events);
            });
        }

        function loadTemplate(name) {
            if(me.options.templates[name]) {
                return;
            }

            $.ajax({
                url: me.options.tmplPath + name + '.html',
                dataType: 'html',
                type: 'GET'
            }).done(function(html) {
                me.options.templates[name] = _.template(html);
            });
        };

        function updateDay() { };

        function updateWeek() { };

        function updateYear() {
            updateMonthYear();
        };

        function updateMonth() {
            updateMonthYear();

            var week = $(document.createElement('div')).attr('id', 'cal-week-box');
            week.html(language.week);
            var start = me.options.position.start.getFullYear() + '-' + me.options.position.start.getMonthFormatted() + '-';
            $('.cal-month-box .cal-row-fluid').each(function(k, v) {
                var row = $(v);
                row.bind('mouseenter',function() {
                    var child = $('.cal-span1:first-child .cal-month-day', row);
                    var day = (child.hasClass('cal-month-first-row') ? 1 : $('[data-cal-date]', child).text());
                    day = (day < 10 ? '0' + day : day);
                    week.attr('data-cal-week', start + day).show().appendTo(child);
                }).bind('mouseleave', function() {
                    week.hide();
                });
            });

            week.click(function() {
                me.options.day = $(this).data('cal-week');
                me.view('week');
            });

            $('a.event').mouseenter(function() {
                $('a.event' + $(this).data('event-id')).parents('.cal-span1').addClass('day-highlight dh-' + $(this).data('event-class'));
            });
            $('a.event').mouseleave(function() {
                $('div.cal-span1').removeClass('day-highlight dh-' + $(this).data('event-class'));
            });
        };

        function update() {
            // this === the element

            $('*[rel="tooltip"]').tooltip();

            $('*[data-cal-date]').click(function() {
                me.options.day = $(this).data('cal-date');
                me.view($(this).data('cal-view'));
            });
            $('.cal-cell').dblclick(function() {
                me.options.day = $('[data-cal-date]', this).data('cal-date');
                me.view($('[data-cal-date]', this).data('cal-view'));
            });

            eval(('update' + me.options.view.capitalize()))()
        };

        function updateMonthYear() {
            if(!me.options.views[me.options.view].slide_events) {
                return;
            }
            var activecell = 0;

            var downbox = $(document.createElement('div')).attr('id', 'cal-day-box').html('<i class="icon-chevron-down"></i>');

            $('.cal-month-day, .cal-year-box .span3').each(function(k, v) {
                $(v).bind('mouseenter', function() {
                    if($('.events-list', v).length == 0) return;
                    if($(v).children('[data-cal-date]').text() == activecell) return;
                    downbox.show().appendTo(v);
                });
                $(v).bind('mouseleave', function() {
                    downbox.hide();
                });
            });


            var slider = $(document.createElement('div')).attr('id', 'cal-slide-box');
            slider.hide().click(function(event) {
                event.stopPropagation();
            });

            loadTemplate('events-list');

            downbox.click(function(event) {

                event.stopPropagation();

                var $me = $(this);
                var cell = $me.parents('.cal-cell');
                var row = $me.parents('.cal-row-fluid');
                var tick_position = cell.data('cal-row');

                $me.fadeOut('fast');

                slider.html(me.options.templates['events-list']({events: $('.events-list a.event', cell)}))
                    .slideUp('fast', function() {
                        row.after(slider);
                        activecell = $('[data-cal-date]', cell).text();
                        $('#cal-slide-tick').addClass('tick' + tick_position).show();
                        slider.slideDown('fast', function() {
                            $('body').one('click', function() {
                                slider.slideUp('fast');
                                activecell = 0;
                            });
                        });
                    });

                $('a.event-item').mouseenter(function() {
                    $('a.event' + $(me).data('event-id')).parents('.cal-span1').addClass('day-highlight dh-' + $(me).data('event-class'));
                });
                $('a.event-item').mouseleave(function() {
                    $('div.cal-span1').removeClass('day-highlight dh-' + $(me).data('event-class'));
                });
            });
        };

        /* PUBLIC FUNCTIONS */
        this.setOptions = function(object) {
            $.extend(me.options, object);
        }

        this.view = function(view) {
            var oldView = me.options.view;
            if(view) me.options.view = view;

            initPosition();
            loadEvents();
            render();

            if (view && oldView != view) // Verify if the view was really changed
                $(me).triggerHandler('viewchange', me.options.view);

            // return the active view
            return me.options.view;
        };

        this.navigate = function(where, next) {
            var to = $.extend({}, me.options.position);
            if(where == 'next') {
                switch(me.options.view) {
                    case 'year':
                        to.start.setFullYear(me.options.position.start.getFullYear() + 1);
                        break;
                    case 'month':
                        to.start.setMonth(me.options.position.start.getMonth() + 1);
                        break;
                    case 'week':
                        to.start.setDate(me.options.position.start.getDate() + 7);
                        break;
                    case 'day':
                        to.start.setDate(me.options.position.start.getDate() + 1);
                        break;
                }
            } else if(where == 'prev') {
                switch(me.options.view) {
                    case 'year':
                        to.start.setFullYear(me.options.position.start.getFullYear() - 1);
                        break;
                    case 'month':
                        to.start.setMonth(me.options.position.start.getMonth() - 1);
                        break;
                    case 'week':
                        to.start.setDate(me.options.position.start.getDate() - 7);
                        break;
                    case 'day':
                        to.start.setDate(me.options.position.start.getDate() - 1);
                        break;
                }
            } else if(where == 'today') {
                to.start.setTime(new Date().getTime());
            }
            else {
                $.error(language.error_where.format(where))
            }
            me.options.day = to.start.getFullYear() + '-' + to.start.getMonthFormatted() + '-' + to.start.getDateFormatted();
            me.view();
            if(_.isFunction(next)) {
                next();
            }

            $(me).triggerHandler('navigate', where, me.getStartDate(), me.getEndDate());
        };

        this.getTitle = function() {
            var p = me.options.position.start;
            switch(me.options.view) {
                case 'year':
                    return language.title_year.format(p.getFullYear());
                    break;
                case 'month':
                    return language.title_month.format(language['m' + p.getMonth()], p.getFullYear());
                    break;
                case 'week':
                    return language.title_week.format(p.getWeek(), p.getFullYear());
                    break;
                case 'day':
                    return language.title_day.format(language['d' + p.getDay()], p.getDate(), language['m' + p.getMonth()], p.getFullYear());
                    break;
            }
            return;
        };

        this.isToday = function() {
            var now = new Date().getTime();

            return ((now > me.options.position.start) && (now < me.options.position.end));
        };

        this.getStartDate = function() {
            return me.options.position.start;
        };

        this.getEndDate = function() {
            return me.options.position.end;
        };

        // refresh the view for the first time
        me.view();

        return me;
    }

    function getEasterDate(year) {
        var a = year % 19;
        var b = Math.floor(year / 100);
        var c = year % 100;
        var d = Math.floor(b / 4); 
        var e = b % 4;
        var f = Math.floor((b + 8) / 25);
        var g = Math.floor((b - f + 1) / 3); 
        var h = (19 * a + b - d - g + 15) % 30;
        var i = Math.floor(c / 4);
        var k = c % 4;
        var l = (32 + 2 * e + 2 * i - h - k) % 7;
        var m = Math.floor((a + 11 * h + 22 * l) / 451);
        var n0 = (h + l + 7 * m + 114)
        var n = Math.floor(n0 / 31) - 1;
        var p = n0 % 31 + 1;
        return new Date(year, n, p, 0, 0, 0);
    }

    $.fn.calendar = function(params) {
        return new Calendar(params, this);
    }
}(jQuery));
